#include "bomloader.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <set>
#include <cstdarg>
using namespace std;

uint maxAmong(uint n_args, ...)
{
    va_list ap;
    va_start(ap, n_args);
    uint max = va_arg(ap, uint);
    for(uint i = 2; i <= n_args; i++) {
        uint a = va_arg(ap, uint);
        if(a > max) max = a;
    }
    va_end(ap);
    return max;
}

vector<string> tool_split(char* line)
{
    vector<string> parts;
    char* start = line;
    for(int i=0; line[i]!=0; ++i) {
        if(!(isprint(line[i]))) {
            line[i]=0;
            char cData[40];
            strcpy(cData,start);
            string str = cData;
            parts.push_back(str);
            start = line + i + 1;
        }
    }
    string lastStr = start;
    parts.push_back(lastStr);

    return parts;
}

BomLoader::BomLoader()
{
}


ItemOrg* BomLoader::findItemOrg(int &itemId, int &orgId)
{
    unsigned key = ItemOrg::genKey(itemId, orgId);

    std::pair <std::multimap<unsigned int, ItemOrg*>::iterator, std::multimap<unsigned int, ItemOrg*>::iterator> ret;
    ret = itemMap.equal_range(key);

    for (std::multimap<unsigned int,ItemOrg*>::iterator it=ret.first; it!=ret.second; ++it)
    {
        ItemOrg* io = it->second;

        if( io->itemId() == itemId && io->orgId() == orgId )
        {
            return io;
        }
    }

    return NULL;
}

void BomLoader::loadBomRecords(const char* bomFile , const char* bomCompFile,
                               uint bomIdx_bomSeqId, uint bomIdx_assyId, uint bomIdx_assyOrgId,
                               uint bomCompIdx_bomSeqId,
                               uint bomCompIdx_assyId,
                               uint bomCompIdx_itemId, uint bomCompIdx_orgId,
                               uint bomCompIdx_usage)
{
    // Load MSLD_BOM.dat
    FILE* file = fopen( bomFile, "r" );

    if( !file )
    {
        cerr<<"Failed to open "<<bomFile<<endl;
        exit(1);
    }

    uint maxBomIdx = maxAmong(3, bomIdx_bomSeqId, bomIdx_assyId, bomIdx_assyOrgId);

    while(!feof(file)){
        char line[5000];
        if(fscanf(file,"%s",line) ==1) {
            if(line[0] ==0) break;
            BomRecord record;

            vector<string> parts = tool_split(line);

            if( parts.size() < maxBomIdx )
            {
                cerr<<"Failed to read all fields from "<<bomFile<<" get "<<parts.size()<<" fields, expect "<<maxBomIdx<<endl;
                exit(1);
            }


            record.bomSeqId = atoi(parts.at(bomIdx_bomSeqId).c_str());
            record.assyId = atoi(parts.at(bomIdx_assyId).c_str());
            record.orgId = atoi(parts.at(bomIdx_assyOrgId).c_str());

            bomRecords.push_back(record);
        }
    }

    fclose(file);

    // Load MSLD_BOM_COMP.dat
    file = fopen( bomCompFile, "r" );

    if( !file )
    {
        cerr<<"Failed to open "<<bomCompFile<<endl;
        exit(1);
    }

    uint maxBomCompIdx = maxAmong(5, bomCompIdx_bomSeqId,bomCompIdx_assyId,
                                 bomCompIdx_itemId, bomCompIdx_orgId,
                                 bomCompIdx_usage);
    while(!feof(file)){
        char line[5000];
        if(fscanf(file,"%s",line) ==1) {
            if(line[0] ==0) break;

            BomCompRecord record;

            vector<string> parts = tool_split(line);

            if( parts.size() < maxBomCompIdx )
            {
                cerr<<"Failed to read all fields from "<<bomFile<<" get "<<parts.size()<<" fields, expect "<<maxBomIdx<<endl;
                exit(1);
            }

            record.bomSeqId = atoi(parts.at(bomCompIdx_bomSeqId).c_str());
            record.assyId = atoi(parts.at(bomCompIdx_assyId).c_str());
            record.compId = atoi(parts.at(bomCompIdx_itemId).c_str());
            record.orgId = atoi(parts.at(bomCompIdx_orgId).c_str());
            record.usage = strtod(parts.at(bomCompIdx_usage).c_str(), NULL);

            bomCompRecords.push_back(record);
        }
    }

    fclose(file);

    //For test
    cout<<" BOM records: "<<bomRecords.size()<<" BOM_COMP records: "<<bomCompRecords.size()<<endl;
}

void BomLoader::filterBomRecords(vector<int> itemIds)
{
    if( itemIds.size() == 0 ) return;

    set<int> itemCluster;

    for( vector<int>::iterator i = itemIds.begin(); i != itemIds.end(); i++ )
    {
        itemCluster.insert((*i));
    }

    bool hasNewItem;
    do{
        hasNewItem = false;
        for( vector< BomCompRecord >::iterator i = bomCompRecords.begin();
             i != bomCompRecords.end();
             i++ )
        {
            if( ! i->needRevisit ) continue;

            if( itemCluster.find(i->assyId) != itemCluster.end() )
            {
                itemCluster.insert(i->compId);
                hasNewItem = true;
                i->needRevisit = false;
            }else if( itemCluster.find(i->compId) != itemCluster.end() )
            {
                itemCluster.insert(i->assyId);
                hasNewItem = true;
                i->needRevisit = false;
            }
        }
    }while( hasNewItem );

    // We don't really need to remove a record from vector, it may takes more time.

    for( vector< BomRecord >::iterator bi = bomRecords.begin();
         bi != bomRecords.end();
         bi++ )
    {
        if( itemCluster.find(bi->assyId) == itemCluster.end() )
        {
            bi->isNeeded = false;
        }
    }

    for( vector< BomCompRecord >::iterator bci = bomCompRecords.begin();
         bci != bomCompRecords.end();
         bci++ )
    {
        if( itemCluster.find(bci->compId) == itemCluster.end() )
        {
            bci->isNeeded = false;
        }
    }
}

void BomLoader::createBoms()
{
    // Create tree 'root' node from data transformed from MSLD_BOM.dat
    for( vector< BomRecord >::iterator bi = bomRecords.begin();
         bi != bomRecords.end();
         bi++ )
    {
        if( ! bi->isNeeded ) continue;

        BomRecord& record = (*bi);

        ItemOrg* assy = new ItemOrg(record.assyId,record.orgId);
        assy->setTree(true);
        assemblyMap[ record.bomSeqId ] = assy;
        bomTrees.push_back(assy);
        assy->setKey(ItemOrg::genKey(record.assyId,record.orgId));
        itemMap.insert( multimap< uint, ItemOrg* >::value_type(assy->key(), assy) );
    }

    // link assembly and tree nodes
    for( vector< BomCompRecord >::iterator bci = bomCompRecords.begin();
         bci != bomCompRecords.end();
         bci++ )
    {
        if( ! bci->isNeeded ) continue;

        BomCompRecord& record = (*bci);

        map< int, ItemOrg* >::iterator ai = assemblyMap.find( record.bomSeqId );

        // Maybe this should never happen, I guess?
        if( ai == assemblyMap.end() ) continue;

        ItemOrg* root = ai->second;

        ItemOrg* comp = findItemOrg( record.compId, record.orgId );

        if( !comp ) // We havn't got such item-org yet
        {
            comp = new ItemOrg( record.compId, record.orgId );
            comp->setKey( ItemOrg::genKey(record.compId, record.orgId));
            itemMap.insert( multimap< uint, ItemOrg* >::value_type(comp->key(), comp) );
        }

        if( comp->isTree() )
        {
            comp->setSubTree(true);
        }
        root->addComponent(comp, record.usage);

        // TODO: This is a co/by product, should also be treated like an assembly
    }
}


void BomLoader::printBoms()
{
    for( vector< ItemOrg* >::iterator bi = bomTrees.begin();
         bi != bomTrees.end();
         bi++ )
    {
        ItemOrg& rTree = (**bi);

        // Skip trees that is part of other trees ( to save output )
        if( rTree.isSubTree() ) continue;

        cout<<"---------------------------------"<<endl;
        rTree.print();
        cout<<endl;
    }
}
